﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;

using Newtonsoft.Json.Linq;

using Utils;
using Utils.Buffer;

namespace Microsoft.AspNetCore.Mvc
{
    public abstract class AuthIdentity : ClaimsIdentity
    {
        private readonly static string aesKey = "testjjjj".ToMD5Bit16();


        public override string AuthenticationType => this.GetType().FullName;


        public virtual bool Is(Type[] userTokenTypes)
        {
            return userTokenTypes.Length == 0 || userTokenTypes.Any(a => a.FullName == AuthenticationType|| a.Equals(typeof(AnonymousToken)));
        }

        public string AssemblyName => this.GetType().Assembly.GetName().Name;

        public static AuthIdentity CreateByToken(IServiceProvider serviceProvider, string authorizationToken, bool allowAnonymous)
        {
            if (string.IsNullOrEmpty(authorizationToken) && allowAnonymous)
                return new AnonymousToken();
            if (string.IsNullOrEmpty(authorizationToken))
                throw Error.CustomError(ErrorType.AuthorizationError, "请先登录");

            var buffer = ByteReadBuffer.Wrap(AESDecrypt(authorizationToken, aesKey));
            long expiryTime = buffer.ReadLong();
            string assemblyName = buffer.ReadUnknownString();
            string authenticationType = buffer.ReadUnknownString();
            var bytes = buffer.ReadEnd();

            if (expiryTime < DateTime.Now.ToUnixTimestamp())
            {
                throw Error.CustomError(ErrorType.AuthorizationError, "票据过期");
            }

            var token = ActivatorUtilities.CreateInstance(serviceProvider, Assembly.Load(assemblyName).GetType(authenticationType)) as AuthIdentity;
            InjectionFactory.ImportsFormFactory(serviceProvider, token);
            token.Deserialize(bytes);
            return token;
        }

        public virtual bool Verify(string rightCode)
        {
            return true;
        }

        public static List<string> GetApiList<T>(string prefix) where T : AuthIdentity
        {
            var type = typeof(T);
            var controllerTypes = Assembly.GetEntryAssembly().GetTypes()
                   .Where(a => !a.IsSealed && (a.Namespace?.Contains("Controllers") ?? false))
                   .ToList();
            List<string> apis = new List<string>();
            foreach (var controllerType in controllerTypes)
            {

                var menbers = controllerType.GetMembers(BindingFlags.Public | BindingFlags.Instance);

                foreach (var menber in menbers)
                {
                    if (TryGetUrl(menber, type, out string url))
                    {
                        apis.Add(prefix + url);
                    }
                }
            }
            return apis;
        }

        private static bool TryGetUrl(MemberInfo member, Type type, out string url)
        {
            var controllerType = member.ReflectedType;
            var route = controllerType.GetCustomAttribute<RouteAttribute>();
            var method = member.GetCustomAttribute<HttpMethodAttribute>();
            var authAttr = controllerType.GetCustomAttribute<AuthAttribute>();
            authAttr = member.GetCustomAttribute<AuthAttribute>() ?? authAttr;
            if (method == null || authAttr == null || !authAttr.IsValidate || !authAttr.UserTokenTypes.Contains(type))
            {
                url = null;
                return false;
            }
            string template = string.Concat("/", route.Template, "/", method.Template);
            if (template.Contains("[controller]"))
            {
                var controllerName = controllerType.Name.EndsWith("Controller") ? controllerType.Name.Substring(0, controllerType.Name.Length - 10) : controllerType.Name;
                template = template.Replace("[controller]", controllerName.ToLower());
            }
            if (template.Contains("[action]"))
            {
                template = template.Replace("[action]", member.Name.ToLower());
            }
            url = Regex.Replace(template, @"\{[a-zA-Z:]+\}", "*");
            return true;
        }



        protected abstract bool Deserialize(byte[] data);



        protected abstract byte[] Serialize();


        public string ToTokenString(long expired = 3600 * 24 * 7)
        {
            var bs = Serialize();
            var bytes = ByteWriteBuffer.Alloc(bs.Length + 12 + AssemblyName.Length)
              .Write(expired + DateTime.Now.ToUnixTimestamp()) // 过期时间
              .WriteUnknownString(AssemblyName, Encoding.ASCII)
              .WriteUnknownString(AuthenticationType, Encoding.ASCII)
              .Write(bs);
            return AESEncrypt(bytes.ToBytes(), aesKey);
        }

        /// <summary>
        /// ASE 加密 ECB
        /// </summary> 
        /// <returns></returns>
        private static string AESEncrypt(byte[] encryptStr, string aesKey)
        {
            byte[] keyArray = new byte[32];
            Array.Copy(Encoding.UTF8.GetBytes(aesKey), keyArray, Math.Min(aesKey.Length, 32));
            var rDel = Aes.Create();
            rDel.Key = keyArray;
            rDel.Mode = CipherMode.ECB;
            rDel.Padding = PaddingMode.PKCS7;
            ICryptoTransform cTransform = rDel.CreateEncryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(encryptStr, 0, encryptStr.Length);
            return Convert.ToBase64String(resultArray, 0, resultArray.Length);
        }


        /// <summary>
        /// AES 解密
        /// </summary>
        /// <param name="decryptStr"></param>
        /// <param name="aesKey"></param>
        /// <returns></returns>
        private static byte[] AESDecrypt(string decryptStr, string aesKey)
        {
            byte[] keyArray = new byte[32];
            Array.Copy(Encoding.UTF8.GetBytes(aesKey), keyArray, Math.Min(aesKey.Length, 32));
            byte[] toEncryptArray = Convert.FromBase64String(decryptStr);
            var rDel = Aes.Create();
            rDel.Key = keyArray;
            rDel.Mode = CipherMode.ECB;
            rDel.Padding = PaddingMode.PKCS7;
            ICryptoTransform cTransform = rDel.CreateDecryptor();
            return cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

        }
    }

}
